﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using Protocol.Transport;

namespace Protocol
{
    public static class Sugar
    {
        public static byte[] AES256IGEEncrypt(this byte[] src, byte[] iv, byte[] key)
        {
            var engine = new AesEngine();
            engine.init(true, new KeyParameter(key));

            int blocksCount = src.Length / 16;
            byte[] outData = new byte[src.Length];

            byte[] curIvX = iv;
            byte[] curIvY = iv;
            int curIvXOffset = 16;
            int curIvYOffset = 0;

            for (int i = 0; i < blocksCount; i++)
            {
                int offset = i * 16;

                for (int j = 0; j < 16; j++)
                {
                    outData[offset + j] = (byte)(src[offset + j] ^ curIvY[curIvYOffset + j]);
                }

                engine.processBlock(outData, offset, outData, offset);
                //engine.TransformBlock(outData, offset, outData.Length, outData, offset);
                for (int j = 0; j < 16; j++)
                {
                    outData[offset + j] = (byte)(outData[offset + j] ^ curIvX[curIvXOffset + j]);
                }

                curIvX = src;
                curIvXOffset = offset;
                curIvY = outData;
                curIvYOffset = offset;
            }

            return outData;
        }

        public static byte[] AES256IGEDecrypt(this byte[] src, byte[] iv, byte[] key)
        {
            var engine = new AesEngine();
            //var p = new AesCryptoServiceProvider(){Key = key};
            //var engine = p.CreateDecryptor(key, iv);
            engine.init(false, new KeyParameter(key));

            int blocksCount = src.Length / 16;
            byte[] outData = new byte[src.Length];

            byte[] curIvX = iv;
            byte[] curIvY = iv;
            int curIvXOffset = 16;
            int curIvYOffset = 0;

            for (int i = 0; i < blocksCount; i++)
            {
                int offset = i * 16;

                for (int j = 0; j < 16; j++)
                {
                    outData[offset + j] = (byte)(src[offset + j] ^ curIvX[curIvXOffset + j]);
                }
                //engine.TransformBlock(outData, offset, outData.Length, outData, offset);
                engine.processBlock(outData, offset, outData, offset);
                for (int j = 0; j < 16; j++)
                {
                    outData[offset + j] = (byte)(outData[offset + j] ^ curIvY[curIvYOffset + j]);
                }

                curIvY = src;
                curIvYOffset = offset;
                curIvX = outData;
                curIvXOffset = offset;
            }

            return outData;
        }


        public static void Send(this object message, DataChannel channel)
        {
            channel.Send(message);
        }

        public static byte[] ToByteArray(this IEnumerable<int> items)
        {
            return items.Select(i => (byte)(i >= 0 ? i : i + 256)).ToArray();
        }

        private static Random _random = new Random();

        public static byte[] GetRandomBytes(int count)
        {
            var buffer = new byte[count];
            _random.NextBytes(buffer);
            return buffer;
        }

        public static double ToUnixTimeStamp(this DateTime dateTime)
        {
            return dateTime.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
        }

        public static byte[] ToUnixTimeStampBytes(this DateTime dateTime)
        {
            return BitConverter.GetBytes(dateTime.ToUnixTimeStamp());
        }

        public static DateTime FromUnixTimeStamp(this double unixTimeStamp)
        {
            var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

        public static DateTime FromUnixTimeStamp(this byte[] unixTimeStampBytes)
        {
            return BitConverter.ToDouble(unixTimeStampBytes, 0).FromUnixTimeStamp();
        }

        public static T[] AlignTo<T>(this T[] data, int length)
        {
            var alignedData = new T[length];
            Array.Copy(data, alignedData, length > data.Length ? data.Length : length);
            return alignedData;
        }

        public static T[] AlignBy<T>(this List<T> data, int align) where T : new()
        {
            while (data.Count % align != 0) data.Add(new T());
            return data.ToArray();
        }

        public static T[] AlignBy<T>(this IList<T> data, int align) where T: new()
        {
            var alignedData = new List<T>(data);
            while (alignedData.Count % align != 0)
            {
                alignedData.Add(new T());
            }

            return alignedData.ToArray();
        }

        public static T[] Substring<T>(this T[] bytes, int skip, int take)
        {
            return bytes.Skip(skip).Take(take).ToArray();
        }

        private static readonly SHA1 SHA1 = new SHA1Managed();

        public static byte[] Hash(this byte[] bytes)
        {
            return SHA1.ComputeHash(bytes);
        }
    }
}
